iT邦幫忙

2022 iThome 鐵人賽

DAY 8
0

[Day08] Clojure Functional Programming與資料操作(1) - map

我們在第一週簡單地帶大家Clojurescript的基礎語法,有些篇幅也把clojure和跟Javascript或其他語言做對照,讓大家更易上手、也搞懂常見定義function的方式及資料結構:

Day01 探索Clojure / Clojurescript 開賽宣言
Day02 學習Clojure語法的勇敢與真實
Day03 Javascript vs Clojurescript - def & defn 比較
Day04 Clojure data structure之collection - Vectors
Day05 Clojure data structure之collection - Lists
Day06 Clojure data structure之collection - Maps
Day07 Clojure data structure之collection - Sets

接下來幾天就focus在Functional Programming的基礎函式運用 -
三法寶: map / reduce / filter

並且也要注意這些函式會回傳哪一種資料結構喔!

map 函式

我們不要把今天的map函式和第六天的 Map資料結構搞混囉!(map function通常用小寫表示)

首先從數字number的好用的API叫inc
使用方式是 (inc x),會回傳x +1的數字 (Returns a number one greater than num.)

(inc 1)
=> 2


(inc 3.1)
=> 4.1

如果是想對一個vector裡的每一個數字 +1呢?

(inc [1 2 3])
=> Execution error (ClassCastException)
class clojure.lang.PersistentVector cannot be cast to class java.lang.Number (clojure.lang.PersistentVector is in unnamed module of loader 'app'; java.lang.Number is in module java.base of loader 'bootstrap')

ClassCastException說明了vector無法轉換成 numbers

用map來改裝一下,就ok了!

至於為什麼會這樣呢?

來閱讀一下map function的說明Ref

並且試著進一步去拆解:

map搭配一個collection

在我們舉的例子裡,

(map inc [1 3 5 7 9])
; => (2 4 6 8 10)

對照一下是說明的:(map f c1)

(map inc [1 3 5 7 9])裡collection只有一組,map函式會對每個在collection的element套用某個function
例如inc,map讓vector裡的每個數字 +1,並回傳一個看起來很像list的東東

但又不確定是什麼?

那我們可以用class來查查看,這個是LazySeq

(class (map inc [1 2 3]))
=> clojure.lang.LazySeq

;;跟一般的list不一樣
(class (list 1 2 3))
=> clojure.lang.PersistentList

注意,map不會回傳 vector喔!

map搭配匿名函式

Day03 Javascript vs Clojurescript - def & defn 比較時有聊果
這裡我們想介紹匿名函式的簡短寫法 #()

(fn [X Y] (str X Y))

#(str %1 %2)

是一樣的

再搭配map的話,這樣我們就可以對Vector裡的一個一個的值做匿名函數裡想要的操作
例如把字串串起來。

來跟我家兩隻貓貓們說早安吧!

 (map #(str "Good Morning, " %) ["Goma" "Cara"])
=> ("Good Morning, Goma" "Good Morning, Cara")

貓貓:我們還想繼續睡!

用def及匿名函數 / defn作出自己的increment

第二天講解過了最簡單的(+ 1 1)+1這樣的函式當然可以自己實作囉

(def increment (fn[x] (+ x 1)))
=> #'tutorial.core/increment

 (increment 3.1)
=> 4.1

;; def 換成 defn寫法

(defn increment-f [x] (+ x 1)) 
=> #'tutorial.core/increment-f

 (increment-f 3.1)
=> 4.1

用map函式操作定義好的increment

如同最上面的 inc例子,
假設我們有自己寫好的function times-20 (乘以20倍),
若想要對vector的每個element操作相同的函式,
也是可以照著手冊上說的 (map f col)來使用

(def times-20 (fn[x] (* x 5 4)))

;; 用map做出一個function
(defn multiple_vector
  []
  (map times-20 [1 3 5 7 9]))

tutorial.core=>   (multiple_vector)
=> (20 60 100 140 180)

first-class functions 一級函式 / 一等公民

從前面inc / increment / 到剛剛的 times-20 例子,

恰好說明了一級函式(first-class function)的特性,
函式是頭等公民,可被當成是, (functions can be treated as values)
這個頭等公民超厲害的,有多項特色:

  1. 可作為別的函式的參數 first-class can be treated as values
  2. 可作為別的函式的返回值 first-class can be returned from functions
  3. 可賦值給變量 first-class can be assigned as values
  4. 可儲存在資料結構中。

function 可帶多個參數

以這兩個例子來說,

(+ 1 2 ...n) 
(* 5 4 ...n)

(+ 1 2 ...n)+函式代表對第一個數字進行加法,計算完後再丟給下一個去加
(* 5 4 ...n)*函式代表對第一個數字都進行乘法,計算完後再丟給下一個去乘

(+ 1 2 3 4...)

  • 1完的結果再丟給 2,+2完的結果丟給3,+3完的結果丟給4...

這樣的寫法和其他語言的 (1 + 1) 寫法很不同,
也讓clojure的+ function後面可以接很多個參數!
某位同事的這篇文章就說難怪clojure沒有其他語言的sum,因為+就是sum

function眾生平等

在clojure裡,=+也是個function,-也是個function
incmap也是function,這些和自定義(defn)的function是同個level的。function眾生平等。

All Functions Are Created Equal
One final note: Clojure has no privileged functions. + is just a function, - is just a function, and inc and map are just functions. They’re no better than the functions you define yourself. So don’t let them give you any lip!
Ref:braveclojure Chapter3 do things

map搭配多個collection

前面介紹了Numbers的inc function,
接下來用給String的 str舉例map後面接2個collection

(map f c1 c2 c3)

如果再重新複習這段定義的話,

map returns a lazy sequence consisting of the result of applying f to
the set of first items of each coll, followed by applying f to the
set of second items in each coll, until any one of the colls is
exhausted. Any remaining items in other colls are ignored. Function
f should accept number-of-colls arguments.

(map str ["a" "b" "c"] ["x" "y" "z"] ["A" "B" "C"])
=> ("axA" "byB" "czC")

來看map搭配3個collection的分解動作(在以上的例子是以vector為例):

1.先用str把collection的第一個值接起來

編按:
原本在想是否用 (str(str(str "a") "x") "A")這樣表示 (clojure core library內str實作的效果)
但本篇文章若要強調map function特性,一次過丟所有參數進去呼叫function來表達的話,
這裡稍稍修改一下成(str "a" "x" "A")比較好,感謝路過的大大指點)

(str "a" "x" "A")
=> "axA"

接著把collection的第二個值接起來

(str "b" "y" "B")
=> "byB"

接著把collection的第三個值接起來

(str "c" "z" "C")
=> "czC"

2.最後map會傳回一個新的list

(map str ["a" "b" "c"] ["x" "y" "z"] ["A" "B" "C"])
=> ("axA" "byB" "czC")

小結

function是一等公民每個function眾生平等的概念真的好特別,值得多看code、多練習一點題目唷!

明後天繼續介紹也是超級常用的reduce及filter~

延伸思考

  • Clojure map 原生支援多個collections真的是太奇妙的設計(拍手鼓掌~)為什為其他OOP語言的map就做不到同樣的效果呢?

上一篇
[Day07] Clojure data structure之collection系列(4) Sets
下一篇
[Day09] Clojure Functional Programming與資料操作(2) - reduce
系列文
後端Developer實戰ClojureScript: Reagent與前端框架 Reframe30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言